/*************************************************************************
 * PLEASE SEE THE FILE "license.txt" (INCLUDED WITH THIS SOFTWARE PACKAGE)
 * FOR LICENSE AND COPYRIGHT INFORMATION.
 *************************************************************************/

/* ---------------------------------------------------------------------
                     Preference Management Routines

   Make_preference() creates a new preference structure of the given type
   with the given id/attribute/value/referent.  (Referent is only used
   for binary preferences.)  The preference is not yet added to preference
   memory, however.

   Preference_add_ref() and preference_remove_ref() are macros for
   incrementing and decrementing the reference count on a preference.

   Possibly_deallocate_preference_and_clones() checks whether a given
   preference and all its clones have reference_count 0, and deallocates
   them all if they do; it returns true if they were actually deallocated,
   false otherwise.   Deallocate_preference() deallocates a given
   preference.  These routines should normally be invoked only via the
   preference_remove_ref() macro.

   Add_preference_to_tm() adds a given preference to preference memory (and
   hence temporary memory).  Remove_preference_from_tm() removes a given
   preference from PM and TM.

   Process_o_rejects_and_deallocate_them() handles the processing of
   o-supported reject preferences.  This routine is called from the firer
   and passed a list of all the o-rejects generated in the current
   preference phase (the list is linked via the "next" fields on the
   preference structures).  This routine removes all preferences for
   matching values from TM, and deallocates the o-reject preferences when
   done.
--------------------------------------------------------------------- */

#ifndef PREFMEM_H
#define PREFMEM_H

#include "kernel.h"

#include "instantiation.h"
#include "stl_typedefs.h"

/* ------------------------------------------------------------------------
                               Preferences

   Fields in a preference:

      type:  indicates the type of the preference.  This is one of the
             types defined below:  ACCEPTABLE_PREFERENCE_TYPE, etc.

      o_supported:  true iff the preference has o-support

      in_tm:  true iff the preference is currently in temporary memory

      on_goal_list:  true iff the preference is on the list of preferences
                     supported by its match goal (see all_of_goal_next below)

      reference_count:  (see below)

      id, attr, value, referent:  points to the symbols.  Referent is only
                                  used for binary preferences.

      slot:  points to the slot this preference is for.  (NIL if the
        preference is not in TM.)

      next, prev:  used for a doubly-linked list of preferences of the
                   same type in that particular slot

      all_of_slot_next, all_of_slot_prev:  used for a doubly-linked list
          of all preferences (of any type) in that particular slot

      all_of_goal_next, all_of_goal_prev:  used for a doubly-linked list
          of all preferences supported by this particular match goal.
          This is needed in order to remove all o-support from a particular
          goal when that goal is removed from the context stack.

      next_clone, prev_clone:  used for a doubly-linked list of all "clones"
        of this preference.  When a result is returned from a subgoal and a
        chunk is built, we get two copies of the "same" preference, one from
        the subgoal's production firing, and one from the chunk instantiation.
        If results are returned more than one level, or the same result is
        returned simultaneously by multiple production firings, we can get
        lots of copies of the "same" preference.  These clone preferences
        are kept on a list so that we can find the right one to backtrace
        through, given a wme supported by "all the clones."

      inst:  points to the instantiation that generated this preference

      inst_next, inst_prev:  used for a doubly-linked list of all
        existing preferences that were generated by that instantiation

      next_candidate:  used by the decider for lists of candidate values
        for a certain slot

      next_result:  used by the chunker for a list of result preferences

   Reference counts on preferences:
      +1 if the preference is currently in TM
      +1 for each instantiation condition that points to it (bt.trace)
      +1 if it supports an installed context WME

   We deallocate a preference if:
      (1) reference_count==0 and all its clones have reference_count==0
          (hence it couldn't possibly be needed anymore)
   or (2) its match goal is removed from the context stack
          (hence there's no way we'll ever want to BT through it)
------------------------------------------------------------------------ */

typedef struct preference_struct
{
    PreferenceType                  type;               /* acceptable, better, etc. */
    bool                            o_supported;        /* is the preference o-supported? */
    bool                            in_tm;              /* is this currently in TM? */
    bool                            on_goal_list;       /* is this pref on the list for its match goal */
    goal_stack_level                level;
    uint64_t                        reference_count;
    Symbol*                         id;
    Symbol*                         attr;
    Symbol*                         value;
    Symbol*                         referent;

    /* Identity information used by EBC */
    identity_set_quadruple          identities;                  /* identity sets for all four elements */
    identity_quadruple              inst_identities;                /* identities for a preferences in relation to instantiation that created*/
    identity_quadruple              chunk_inst_identities;          /* identities for a result preference in relation to chunk formed*/
    rhs_quadruple                   rhs_func_inst_identities;       /* identities of syms in rhs functions*/
    rhs_quadruple                   rhs_func_chunk_inst_identities; /* identities of syms in chunk instantiation's rhs functions */

    bool_quadruple                  was_unbound_vars;               /* Whether a RHS variable is a newly created unbound RHS var.  Used by re-orderer */
    action*                         parent_action;                  /* Action that created pref.  Used by the explainer */

    struct slot_struct*             slot;
    struct preference_struct*       next, *prev;                            /* dll of pref's of same type in same slot */
    struct preference_struct*       all_of_slot_next, *all_of_slot_prev;    /* dll of all pref's in same slot */
    struct preference_struct*       all_of_goal_next, *all_of_goal_prev;    /* dll of all pref's from the same match goal */
    struct preference_struct*       next_clone, *prev_clone;                /* dll (without header) of cloned preferences (created when chunking) */

    struct instantiation_struct*    inst;
    struct preference_struct*       inst_next, *inst_prev;
    struct preference_struct*       next_candidate;
    struct preference_struct*       next_result;

    unsigned int                    total_preferences_for_candidate;
    double                          numeric_value;
    bool                            rl_contribution;
    double                          rl_rho;                                 /* ratio of target policy to behavior policy */

    wme_set*                        wma_o_set;

    uint64_t                        p_id;                                   /* This is an ID used by DEBUG_PREFERENCE_INVENTORY */
} preference;

preference* make_preference(agent* thisAgent, PreferenceType type, Symbol* id, Symbol* attr, Symbol* value, Symbol* referent = NULL,
                                   const identity_quadruple &o_ids = identity_quadruple(0, 0, 0, 0),
                                   const bool_quadruple &pWas_unbound_vars = bool_quadruple(false, false, false, false));
preference* shallow_copy_preference(agent* thisAgent, preference* pPref);
void cache_preference_if_necessary(agent* thisAgent, preference* pref);
bool possibly_deallocate_preference_and_clones(agent* thisAgent, preference* pref, bool dont_cache = false);
void deallocate_preference(agent* thisAgent, preference* pref, bool dont_cache = false);
void deallocate_preference_contents(agent* thisAgent, preference* pref, bool dont_cache);
bool add_preference_to_tm(agent* thisAgent, preference* pref);
void remove_preference_from_tm(agent* thisAgent, preference* pref);
bool remove_preference_from_clones_and_deallocate(agent* thisAgent, preference* pref);
void process_o_rejects_and_deallocate_them(agent* thisAgent, preference* o_rejects, preference_list& bufdeallo);
inline bool preference_is_unary(byte p) { return (p < 9);}
inline bool preference_is_binary(byte p) { return (p > 8); }
void clear_preference_list(agent* thisAgent, cons* &lPrefList);

inline void preference_add_ref(preference* p)
{
    (p)->reference_count++;
}

inline bool preference_remove_ref(agent* thisAgent, preference* p, bool dont_cache = false)
{
    if ((p)->reference_count != 0) (p)->reference_count--;
    if ((p)->reference_count == 0) return possibly_deallocate_preference_and_clones(thisAgent, p, dont_cache);
    return false;
}

inline const char* preference_name(byte pNum)
{
    if (pNum == ACCEPTABLE_PREFERENCE_TYPE) return "acceptable";
    if (pNum == REQUIRE_PREFERENCE_TYPE) return "require";
    if (pNum == REJECT_PREFERENCE_TYPE) return "reject";
    if (pNum == PROHIBIT_PREFERENCE_TYPE) return "prohibit";
    if (pNum == RECONSIDER_PREFERENCE_TYPE) return "reconsider";
    if (pNum == UNARY_INDIFFERENT_PREFERENCE_TYPE) return "unary indifferent";
    if (pNum == UNARY_PARALLEL_PREFERENCE_TYPE) return "unary parallel";
    if (pNum == BEST_PREFERENCE_TYPE) return "best";
    if (pNum == WORST_PREFERENCE_TYPE) return "worst";
    if (pNum == BINARY_INDIFFERENT_PREFERENCE_TYPE) return "binary indifferent";
    if (pNum == BINARY_PARALLEL_PREFERENCE_TYPE) return "binary parallel";
    if (pNum == BETTER_PREFERENCE_TYPE) return "better";
    if (pNum == WORSE_PREFERENCE_TYPE) return "worse";
    if (pNum == NUMERIC_INDIFFERENT_PREFERENCE_TYPE) return "numeric indifferent";

    return "illegal preference type";
}

#endif
